/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef ICONFIGURABLE_H
#define ICONFIGURABLE_H

#include <string>
#include <iosfwd>
#include <stdexcept>
#include <boost/smart_ptr.hpp>

#include "SimFile/ConfigWriter.h"

#ifndef __MEX_COMPILER
#include <QMetaType>
#endif

namespace RafMat
{
namespace SimInterface
{

// Deklaracje zapowiadające
class iconf_error;
class IConfigurable;

/** Alias na zręczny wskaźnik na obiekty IConfigurable */
typedef boost::shared_ptr<IConfigurable> IConfigurableSPtr;

/**
    Interfejs pozwalający na modyfikacę pól klasy za pomocą łańcuchów znakowych.
    Klasa która implementuje ten interfejs może być modyfikowana
    poprzez proste komendy tekstowe. W ten sposób GUI może modyfikować parametry
    obiektów symulacji, oraz odczytywać je w celu zapisania konfiguracji pliku.

    Przykład dla DiscreteObjectParams:
    \code
    obj.setParameter("delay", 4);
    obj.getParameter("apoly"); // Zwraca --> [0, 2, 4]
    \endcode

    Interfejs powinien dbać o bezpieczeństwo wyjątów, ze względu na różne śmieci,
    które użytkownik może przesłać.

    Obiektowi konfiguracyjnemu nadajemy zawsze pewną nazwę. Unikalność nazw nie
    jest obligatoryjna, ale zawsze pomaga jakoś identyfikować obiekty, w szczególności
    gdy rzucony zostanie wyjątek.

    \todo Zmusić klasy do obsługi parametru "*", który oznacza ustawienie wszystkich
    parametrów naraz. Jest to zawsze jakieś rozwiązanie ku bezpieczeństwu transakcyjnemu.
*/
class IConfigurable
{
    /** Przeciążony operator wysłania konfiguracji do strumienia */
    friend SimFile::ConfigWriter& operator << (SimFile::ConfigWriter& o, const IConfigurable& c)
    {
        return c.dumpParameters(o);
    }

public:

    /**
        Konstruktor obiektu.
        Domyślnie ustawia nazwę obiektu na "nienazwany" (ang. \i unnammed).

        \param [in] name Nazwa tego obiektu konfiguracyjnego.
    */
    explicit IConfigurable(std::string name = "Unnamed");

    virtual ~IConfigurable();

    /**
        Zwraca nazwę obiektu.

        \return Łańcuch standardowy z nazwą obiektu.
    */
    std::string getName() const { return m_name; }

    /**
        Ustawia nazwę obiektu.

        \param [in] name Nazwa obiektu.
    */
    void setName(const std::string& name) { m_name = name; }

    /**
        Przypisuje określonemu parametrowi pewną wartość.
        Klasa implementująca ma za zadanie rozróżnić znaczenie parametrów
        oraz zasygnalizować ewentualny błąd użytkownika za pomocą wyjątku std::iconf_error.

        \param [in] param parametr, do którego zapisujemy.
        \param [in] value wartość, którą zapisujemy (w formie łańcucha znaków).
        \throws std::iconf_error - gdy parametr lub wartość nie zgadza się
        syntaktycznie z daną implementacją.
    */
    virtual bool setParameter(const std::string& param, const std::string& value) throw(std::logic_error) = 0;

    /**
        Odczytuje określony parametr.
        Klasa implementująca ma za zadanie rozróżnić czego od niej się chce
        oraz zasygnalizować ewentualny błąd parametru.

        \param [in] param parametr, który chcemy odczytać.
        \return Wartość odpowiadająca parametrowi w formie łańcucha znaków.
        \throws std::iconf_error - gdy parametr nie zgadza się
        syntaktycznie z daną implementacją.
    */
    virtual std::string getParameter(const std::string& param) const throw(std::logic_error) = 0;

    /**
        Wysyła stan obiektu do strumienia.

        \param [in/out] o strumień docelowy.
        \return Strumień przekazany funkcji.
    */
    virtual SimFile::ConfigWriter& dumpParameters(SimFile::ConfigWriter& o) const = 0;

private:
    std::string m_name;

    // Debug:
    static int m_livingObjCount;

public:
    static int debugGetLivingCount() { return m_livingObjCount; }
};

/**
    Klasa wyjątków rzucanych przez IConfigurable

    Klasa jest typu std::logic_error, więc bloki catch mogą przechwytywać
    wyjątki tego typu.

    Klasa oferuje kilka metod statycznych generujących obiekty błędów z
    treścią odpowiadającą konkretnym problemom.
*/
class iconf_error : public std::logic_error
{
public:
    /**
        Konstruktor przypisujący dowolną wiadomość.
        \param [in] __arg Wiadomość przypisana bieżącej instancji.
    */
    explicit iconf_error(const std::string& __arg);

    /**
        Konstruktor tworzący sformatowaną wiadomość.

        \param [in] conf Referencja do obiektu konfiguracyjnego, który rzucił wyjątek.
        \param [in] expr Wyrażenie, które sprawiło problem.
        \param [in] msg Wiadomość wyjaśniająca problem.
    */
    iconf_error(const IConfigurable& conf, const std::string& expr, const std::string& msg);

    /** Wiadomość dotycząca zbyt dużego stopnia wielomianu. */
    static iconf_error tooHighDelay(const IConfigurable& c, const std::string& param,
                                    unsigned int shouldBe, unsigned int thereIs);

    /** Wiadomość dotycząca zbyt dużego d. */
    static iconf_error tooHighD(const IConfigurable& c, const std::string& param,
                                    unsigned int shouldBe, unsigned int thereIs);

    /** Wiadomość dotycząca zbyt dużego opóźnienia. */
    static iconf_error tooHighDegree(const IConfigurable& c, const std::string& param,
                                    unsigned int shouldBe, unsigned int thereIs);

    /** Wiadomość dotycząca źle dobranego czasu przełączenia. */
    static iconf_error badSwitchTime(const IConfigurable& c, const std::string& param,
                                     unsigned int thereIs);

    /** Wiadomość dotycząca nierozpoznanego parametru. */
    static iconf_error unrecognizedParam(const IConfigurable& c, const std::string& param);
\
    /** Wiadomość dotycząca błędnej składni wartości parametru. */
    static iconf_error syntaxError(const IConfigurable& c, const std::string& param);
};

}   // SimInterface namespace
}   // RafMat namespace

#ifndef __MEX_COMPILER
// Definicja metatypu, wymaganego przez QVariant.
Q_DECLARE_METATYPE(RafMat::SimInterface::IConfigurableSPtr)
#endif

#endif // ICONFIGURABLE_H
